package roborescue.examples;

import jason.RoborescueEnv;
import jason.asSyntax.Structure;

import java.rmi.RemoteException;

import robocode.rescue.RobotInfo;
import robocode.rescue.features.Defesa;
import robocode.rescue.features.Movimentacao;
import robocode.rescue.features.RedeNeuralFunctions;
import robocode.rescue.features.RoboAtaque;
import robocode.rescue.interfaces.RMIRobotInterface;

public class TimeLadoDireito extends RoborescueEnv {

    private final int numRobos = 5;
    private RobotInfo[] robos;
    private RobotInfo[] robosInimigos;
    private int wasCalledRNA = 0;

    /**
     * Atributos Ataque!
     *
     * Obs: atributo distanciaAnterior e distanciaAtual estão na classe
     * RobotInfo
     */
    private RoboAtaque[] roboAtaque;
    private int auxRoboDois;
    private int auxRoboTres;

    private double[] inputsAtaqueRNA; // 4 inputs para a RNA de ataque.
    private double[] angulosRobos; //utilizado para conseguir a informação dos ângulos dos robôs.

    private RedeNeuralFunctions rna;

    /**
     * Atributos Defesa! (Shiotani)
     */
    final double PI = Math.PI;          //somente um constante.
    Movimentacao mv = new Movimentacao();
    
    private double[] posicoesAnterioresX = new double[4];
    private double[] posicoesAnterioresY = new double[4];
    private double[] posicaoInterseccaoX = new double[2];
    private double[] posicaoInterseccaoY = new double[2];
    private double[] interseccoes = new double[5];
    private double[][] pontosInterseccoes = new double[4][3];
    private double[] distancia = new double[5];
    private int[] atacantes = new int[3];
    private int[] defensores = new int[3];
    private double distanciaRobo1Refem;
    public Defesa def;
    private int i = 0;
    
    //Para inicializacoes necessarias
    @Override
    public void setup() {
        robos = new RobotInfo[numRobos];
        roboAtaque = new RoboAtaque[numRobos];

        inputsAtaqueRNA = new double[4]; //neste caso ele irá passar os inputs para a rede neural roboinimigo1 = inputsAtaqueRNA[0].
        angulosRobos = new double[numRobos];

        for (int i = 0; i < roboAtaque.length; i++) {
            roboAtaque[i] = new RoboAtaque();
        }
    }

    @Override
    public boolean executeAction(String ag, Structure action) {

        try {
            mainLoop();
            Thread.sleep(20);
        } catch (RemoteException ex) {
            ex.printStackTrace();
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }

        return true;

    }

    public void mainLoop() throws RemoteException {

        robos = getServerRef().getMyTeamInfo(myTeam);
        robosInimigos = getServerRef().getEnemyTeamInfo(myTeam);

        rna = new RedeNeuralFunctions();

        RobotInfo refem = robos[0];
        double xRefem = refem.getX();
        double yRefem = refem.getY();

        //referência do nosso time!!!
        RMIRobotInterface[] teamRef = getTeamRef();

        /**
         * LÓGICA GERAL!!!
         */
        
        defensores[0] = 2;//setta o robo 2 como defensor
        defensores[1] = 4;//setta o robo 4 como defensor
        
        //percorre todos os robôs para dar a eles ações!
        for (int robo = 1; robo < numRobos; robo++) {

            /**
             * LÓGICA ATAQUE! robôs 2 e 3 começam atacando!
             *
             * Obs: Quando o robô inimigo não estiver dentro do range de
             * distância (500px), porém existir um outro(s) dentro do range
             * estipulado será enviado o parâmetro -1 para a RNA!
             */
            //SE O REFÉM NÃO ESTIVER SEGUINDO!
            if (teamRef[0].isFollowing() == 0) {

                if (robo == 1 || robo == 3) {

                    //variável auxiliar para verificar se existe algum robô com menos de 500px de distância!!!
                    auxRoboDois = 0;

                    //buscar distância do robô até todos os outros 5 robôs inimigos.
                    roboAtaque[robo].setDistanciaAtual(mv.distanciaRobo(robos[robo], robosInimigos));

                    //se todas as distâncias atuais estiverem maior que 400px então o robô segue seu destino "buscar refém".
                    for (int i = 1; i < roboAtaque[robo].getDistanciaAtual().length; i++) {

                        if (roboAtaque[robo].getDistanciaAtual()[i] < 250 && robos[robo].getX() > robosInimigos[i].getX()) {
                            auxRoboDois++;
                        }

                    }

                    //se a distância para todos os robôs for maior que 500px ele irá seguir seu caminho normal em busca do refém.
                    if (auxRoboDois == 0) {

                        System.out.println("indo em direção ao refém.");
                        mv.goTo(robos[0].getX(), robos[0].getY(), robos[robo], teamRef[robo]);
                        wasCalledRNA = 0;
                    } else { //lógica para chamar a RNA ataque.

                        //auxRoboDois = 0;
                        //ângulos para todos os robôs.
                        angulosRobos = mv.angulosRobo(robos[robo], robosInimigos);

                        for (int i = 1; i < roboAtaque[robo].getDistanciaAtual().length; i++) {

                            //LÓGICA importante - adicionando informação no array de Input para a RNA.
                            if (roboAtaque[robo].getDistanciaAtual()[i] < 250 && robos[robo].getX() > robosInimigos[i].getX()) {

                                //lógica OK - irá adicionar o ângulo do robô no array de input para a RNA de ataque.
                                inputsAtaqueRNA[i - 1] = mv.anguloRNA(angulosRobos[i]);
                            } else {

                                //lógica NOK - irá adicionar o ângulo default (360) no array de input para a RNA de ataque.
                                //inputsAtaqueRNA[i-1] = 360;
                                inputsAtaqueRNA[i - 1] = 0;
                            }

                        }

                        //chama a RNA com os inputs!!!
                        //chama a rna ex - rnaataque(inputsAtaqueRNA[0], inputsAtaqueRNA[1], inputsAtaqueRNA[2], inputsAtaqueRNA[3]);                            
                        System.out.println("chamando rede neural, desviando!");

                        
                        //verificação - se auxRoboDois for "0" então não precisará chamar a RNA e o caminho do robô deverá seguir para o refém;
                        //caso contrário irá chamar a RNA de ataque!
                        //chama a RNA com os inputs!!!
                        double angulo = rna.getNeuralAngleDesvio(inputsAtaqueRNA[0] / 100, inputsAtaqueRNA[1] / 100, inputsAtaqueRNA[2] / 100, inputsAtaqueRNA[3] / 100);

                            //chama a rna ex - rnaataque(inputsAtaqueRNA[0], inputsAtaqueRNA[1], inputsAtaqueRNA[2], inputsAtaqueRNA[3]);
                        //robo usa o retorno da rna para conseguir desviar dos inimigos.

                        if (angulo == 60) {
                            mv.goToAngulo(300, robos[robo], teamRef[robo]);
                            System.out.println("robô: " + robos[robo].getName() + " desviando -> 300º");
                        } else {
                            mv.goToAngulo(240, robos[robo], teamRef[robo]);
                            System.out.println("robô: " + robos[robo].getName() + " desviando -> 240º");
                        }

                    } //fim lógica RNA ataque!

                    //chama o método de "verificação robô bloqueado"...
                    roboAtaque[robo].roboBloqueado(robos[robo], teamRef[robo]);

                }

            } //SE O REFÉM ESTIVER SEGUINDO!!!!! (basicamente a lógica inversa!)
            else {

                if (robo == 1 || robo == 3) {
                    //variável auxiliar para verificar se existe algum robô com menos de 500px de distância!!!
                    auxRoboDois = 0;

                    //buscar distância do robô até todos os outros 5 robôs inimigos.
                    roboAtaque[robo].setDistanciaAtual(mv.distanciaRobo(robos[robo], robosInimigos));

                    //se todas as distâncias atuais estiverem maior que 400px então o robô segue seu destino "buscar refém".
                    for (int i = 1; i < roboAtaque[robo].getDistanciaAtual().length; i++) {

                        if (roboAtaque[robo].getDistanciaAtual()[i] < 250 && robos[robo].getX() < robosInimigos[i].getX()) {
                            auxRoboDois++;
                        }

                    }

                    //se a distância para todos os robôs for maior que 500px ele irá seguir seu caminho normal em busca do refém.
                    if (auxRoboDois == 0) {
                        //mv.goTo(robos[0].getX(), robos[0].getY(), robos[2], teamRef[2]);
                        mv.goTo(2377, 500, robos[robo], teamRef[robo]);
                        System.out.println("voltando com o nosso amigo.");
                    } else { //lógica para chamar a RNA ataque.

                        //ângulos para todos os robôs.
                        angulosRobos = mv.angulosRobo(robos[robo], robosInimigos);

                        for (int i = 1; i < roboAtaque[robo].getDistanciaAtual().length; i++) {

                            //LÓGICA importante - adicionando informação no array de Input para a RNA.
                            if (roboAtaque[robo].getDistanciaAtual()[i] < 250 && robos[robo].getX() < robosInimigos[i].getX()) {

                                //lógica OK - irá adicionar o ângulo do robô no array de input para a RNA de ataque.                            
                                inputsAtaqueRNA[i - 1] = mv.anguloRNA(angulosRobos[i]);

                            } else {

                                //lógica NOK - irá adicionar o ângulo default (360) no array de input para a RNA de ataque.
                                inputsAtaqueRNA[i - 1] = 0;
                            }

                        }

                        //verificação - se auxRoboDois for "0" então não precisará chamar a RNA e o caminho do robô deverá seguir para o refém;
                        //caso contrário irá chamar a RNA de ataque!
                        //chama a RNA com os inputs!!!
                        

                            //chama a rna ex - rnaataque(inputsAtaqueRNA[0], inputsAtaqueRNA[1], inputsAtaqueRNA[2], inputsAtaqueRNA[3]);
                        //robo usa o retorno da rna para conseguir desviar dos inimigos.
                        System.out.println("chamando rede neural, voltando (robô following!");

                        mv.goToAngulo(rna.getNeuralAngleDesvio(inputsAtaqueRNA[0] / 100, inputsAtaqueRNA[1] / 100, inputsAtaqueRNA[2] / 100, inputsAtaqueRNA[3] / 100), robos[robo], teamRef[robo]); //robo usa o retorno da rna para conseguir desviar dos inimigos.

                        System.out.println("robô: " + robos[robo].getName() + " desviando ->" + rna.getNeuralAngleDesvio(inputsAtaqueRNA[0] / 100, inputsAtaqueRNA[1] / 100, inputsAtaqueRNA[2] / 100, inputsAtaqueRNA[3] / 100));

                    } //fim lógica RNA ataque!

                    //chama o método de "verificação robô bloqueado"...
                    roboAtaque[robo].roboBloqueado(robos[robo], teamRef[robo]);
                }

            }

            /**
             * LÓGICA DEFESA! robôs 1 e 4 começam defendendo!
             */
            
//            if(robo == 2 || robo == 4){
//                calculaDistancia();//Define os atacantes
//                //realizaEstrategia();//Executa a logica principal
//                paraCasoInterceptado();//O robo "fica parado" caso tenha interceptado
//            }

        } //fim - laço percorrer todos os robôs.
        
        /**
          * LÓGICA DEFESA! robôs 1 e 4 começam defendendo!
          */
        calculaDistancia();//Define os atacantes
        //realizaEstrategia();//Executa a logica principal
        paraCasoInterceptado();//O robo "fica parado" caso tenha interceptado
    }

    @Override
    public void end() {
        try {
            super.getEnvironmentInfraTier().getRuntimeServices().stopMAS();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.exit(1);
        }
    }

    public static void main(String[] args) {
        TimeLadoDireito team = new TimeLadoDireito();
        team.init(new String[]{"TimeLadoDireito", "localhost"});
        team.setup();
        while (true) {
            try {
                team.mainLoop();
                Thread.sleep(20);
            } catch (RemoteException ex) {
                ex.printStackTrace();
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }
    
    //MÉTODOS DEFESA SHIOTANI!
    public double[] enviaNaPosicaoDoInimigo(RobotInfo[] roboInimigos, int k) {
        double[] posicaoInimigo = new double[2];
        posicaoInimigo[0] = roboInimigos[k].getX();
        posicaoInimigo[1] = roboInimigos[k].getY();

        return posicaoInimigo;
    }

    public int[] calculaDistancia() throws RemoteException {//verifica a distancia de todos os robos inimigos em relação ao nosso refém
        int k = 0;
        for (int j = 1; j < 5; j++) {
            distancia[j] = getRange(robosInimigos[j].getX(), robosInimigos[j].getY(), robos[0].getX(), robos[0].getY());
            atacantes[2] = (int) distancia[j];
            //System.out.println("Distancia do robo "+j+" :" +new DecimalFormat("0.00").format(distancia[j]));
            if (distancia[j] > 1000) {//verifica quais robos inimigos são atacantes
                atacantes[k] = j;
                k++;
            }
        }
        return atacantes;
    }
    
    void paraCasoInterceptado() throws RemoteException{
        RMIRobotInterface[] teamRef = getTeamRef();
        boolean verificador = true;
        for (int k = 0; k < 2 ; k++){
            for (int l = 0; l < 2 ; l++){
                if(getRange(robosInimigos[k].getX(),robosInimigos[k].getY(), robos[l].getX(),robos[l].getY())<=60){
                    //goTo(robos[l].getX(),robos[l].getY(),robos[l],teamRef[l]);
                    verificador = false;
                }
            }
        }if(verificador == true){
            realizaEstrategia();
        }
    }

    void realizaEstrategia() throws RemoteException {
        
        RMIRobotInterface[] teamRef = getTeamRef();
        if (atacantes[0] != 0 || atacantes[1] != 0) {
            int j = 0;
            for (int k = 0; k < 2; k++) {
                for (int l = 0; l < 2; l++) {
                    def = new Defesa(robos, robosInimigos, atacantes[k], defensores[l], posicoesAnterioresX[k], posicoesAnterioresY[k]);
                    def.start();
                    try {
                        def.join();
                        interseccoes = def.getValores();
                    } catch (InterruptedException ie) {
                    }
                    pontosInterseccoes[j][0] = interseccoes[0];
                    pontosInterseccoes[j][1] = interseccoes[1];
                    if (l == 1) {
                        posicoesAnterioresX[k] = interseccoes[2];
                        posicoesAnterioresY[k] = interseccoes[3];
                    }
                    j++;
                }
            }
            
            if (robosInimigos[0].getVelocity() > 0) {
                verificaRefem();
            } else {
                if (getRange(robosInimigos[atacantes[0]].getX(), robosInimigos[atacantes[0]].getY(), robos[defensores[0]].getX(), robos[defensores[0]].getY())
                        < getRange(robosInimigos[atacantes[1]].getX(), robosInimigos[atacantes[1]].getY(), robos[defensores[0]].getX(), robos[defensores[0]].getY())) {
                    goTo(pontosInterseccoes[0][0], pontosInterseccoes[0][1], robos[defensores[0]], teamRef[defensores[0]]);
                } else {
                    goTo(pontosInterseccoes[1][0], pontosInterseccoes[1][1], robos[defensores[0]], teamRef[defensores[0]]);
                }
                if (i > 50) {
                    if (getRange(robosInimigos[atacantes[0]].getX(), robosInimigos[atacantes[0]].getY(), robos[defensores[1]].getX(), robos[defensores[1]].getY())
                            < getRange(robosInimigos[atacantes[1]].getX(), robosInimigos[atacantes[1]].getY(), robos[defensores[1]].getX(), robos[defensores[1]].getY())) {
                        goTo(pontosInterseccoes[2][0], pontosInterseccoes[2][1], robos[defensores[1]], teamRef[defensores[1]]);
                    } else {
                        goTo(pontosInterseccoes[3][0], pontosInterseccoes[3][1], robos[defensores[1]], teamRef[defensores[1]]);
                    }

                }
            }
            i++;
        }
    }

    void verificaRefem() throws RemoteException {
        RMIRobotInterface[] teamRef = getTeamRef();
        distanciaRobo1Refem = getRange(robosInimigos[atacantes[0]].getX(), robosInimigos[atacantes[0]].getY(), robosInimigos[0].getX(), robosInimigos[0].getY());
        if (distanciaRobo1Refem <= 160) {
            System.out.println(robosInimigos[atacantes[0]].getName() + " está com o refem");
            for (int l = 0; l < 2; l++) {
                def = new Defesa(robos, robosInimigos, atacantes[0], defensores[l], robosInimigos[0].getX(), robosInimigos[0].getY());
                def.start();
                try {
                    def.join();
                    interseccoes = def.getValores();
                } catch (InterruptedException ie) {
                }
                goTo(interseccoes[0], interseccoes[1], robos[defensores[l]], teamRef[defensores[l]]);
            }
        } else {
            System.out.println(robosInimigos[atacantes[1]].getName() + " está com o refem");
            for (int l = 0; l < 2; l++) {
                def = new Defesa(robos, robosInimigos, atacantes[1], defensores[l], robosInimigos[0].getX(), robosInimigos[0].getY());
                def.start();
                try {
                    def.join();
                    interseccoes = def.getValores();
                } catch (InterruptedException ie) {
                }
                goTo(interseccoes[0], interseccoes[1], robos[defensores[l]], teamRef[defensores[l]]);
            }
        }
    }

    void goTo(double x, double y, RobotInfo robo, RMIRobotInterface roboInt) throws RemoteException {
        double dist = 40;
        double angle = Math.toDegrees(absbearing(robo.getX(), robo.getY(), x, y));
        double r = turnTo(angle, robo, roboInt);
        roboInt.setAhead(dist * r);
        roboInt.execute();
    }

    /**
     * Gira o menor ângulo para o robô ficar "heading", então retorna a direção
     * que o robô precisa movimentar-se.
     */
    int turnTo(double angle, RobotInfo robo, RMIRobotInterface roboInt) throws RemoteException {
        double ang;
        int dir;
        ang = normaliseBearing(robo.getHeading() - angle);
        if (ang > 90) {
            ang -= 180;
            dir = -1;
        } else if (ang < -90) {
            ang += 180;
            dir = -1;
        } else {
            dir = 1;
        }
        roboInt.setTurnLeft(ang);
        return dir;
    }

    /**
     * Retorna a distância entre dois pontos.
     */
    double getRange(double x1, double y1, double x2, double y2) {
        double x = x2 - x1;
        double y = y2 - y1;
        double range = Math.sqrt(x * x + y * y);
        return range;
    }

    /**
     * Retorna a relação absoluta entre as coordenadas x e y
     *
     */
    public double absbearing(double x1, double y1, double x2, double y2) {

        double xo = x2 - x1;
        double yo = y2 - y1;
        double h = getRange(x1, y1, x2, y2);
        if (xo > 0 && yo > 0) {
            return Math.asin(xo / h);
        }
        if (xo > 0 && yo < 0) {
            return Math.PI - Math.asin(xo / h);
        }
        if (xo < 0 && yo < 0) {
            return Math.PI + Math.asin(-xo / h);
        }
        if (xo < 0 && yo > 0) {
            return 2.0 * Math.PI - Math.asin(-xo / h);
        }
        return 0;
    }

    /**
     * Se uma relação não está dentro do intervalo de-pi a pi, altera para
     * fornecer o menor ângulo
     *
     */
    double normaliseBearing(double ang) {
        if (ang > PI) {
            ang -= 2 * PI;
        }
        if (ang < -PI) {
            ang += 2 * PI;
        }
        return ang;
    }

    //FIM MÉTODOS DEFESA SHIOTANI!

}
